//
//  L0PlugSuitRegistry.m
//  PlugSuit
//
//  Created by ∞ on 10/3/07.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#import "L0PlugSuitRegistry.h"
#import "L0PlugSuitSIMBLRegistry.h"
#import "L0PlugSuitInputManagerRegistry.h"

#import <CoreFoundation/CoreFoundation.h>

#define kL0PlugSuitSharedPreferences (@"net.infinite-labs.PlugSuit")

#define L0PlugSuitLocalizedString(x, y) \
	[self localizedString:(x)]

@implementation L0PlugSuitRegistry

+ (NSArray*) registries {
	static NSArray* registries = nil;
	if (!registries) {
		registries = [[NSArray alloc] initWithObjects:
				[[L0PlugSuitSIMBLRegistry new] autorelease],
				[[L0PlugSuitInputManagerRegistry new] autorelease],
				nil];
	}
	
	return registries;
}

+ (void) install {
	[[self sharedInstance] performSelectorOnMainThread:@selector(installOnThisThread) withObject:nil waitUntilDone:NO];
}

- (void) installOnThisThread {
	NSAutoreleasePool* pool = [NSAutoreleasePool new];
	
	if ([[[NSThread currentThread] threadDictionary] objectForKey:@"L0PlugSuitAlreadyInstalled"])
		return;
	
#ifdef L0PlugSuitDebug
	NSLog(@"[from PlugSuit] DEBUG: PlugSuit is installing itself into %@, pid %d", [NSBundle mainBundle], getpid());
#endif
	
	[[self class] loadPlugIns];
	[self handleErrorsForPlugInsVersionTooHigh:[[self class] versionTooHighOnLastLoad]
								 versionTooLow:[[self class] versionTooLowOnLastLoad]
								  incompatible:[[self class] incompatibleOnLastLoad]];
	
	[[[NSThread currentThread] threadDictionary]
		setObject:[NSNumber numberWithBool:YES] forKey:@"L0PlugSuitAlreadyInstalled"];
		
	[pool release];	
}

- (void) handleErrorsForPlugInsVersionTooHigh:(NSArray*) high versionTooLow:(NSArray*) low incompatible:(NSArray*) incom {
	if ([low count] == 0 && [incom count] == 0 && [high count] == 0)
		return; // all OK
	
	if ([low count] == 0 && [incom count] == 0) {
		if ([high count] > 1)
			[self presentVersionTooHighForPlugIns:high];
		else
			[self presentVersionTooHighForPlugIn:[high objectAtIndex:0]];
	} else if ([high count] == 0 && [incom count] == 0) {
		if ([low count] > 1)
			[self presentVersionTooLowForPlugIns:low];
		else
			[self presentVersionTooLowForPlugIn:[low objectAtIndex:0]];
	} else if ([high count] == 0 && [low count] == 0) {
		if ([incom count] > 1)
			[self presentIncompatibleForPlugIns:incom];
		else
			[self presentIncompatibleForPlugIn:[incom objectAtIndex:0]];
	} else
		[self presentErrorsForVersionTooHigh:high versionTooLow:low incompatible:incom];
		
	// disable all of them.
	NSMutableArray* arr = [NSMutableArray array];
	[arr addObjectsFromArray:high]; [arr addObjectsFromArray:low]; [arr addObjectsFromArray:incom];
	NSEnumerator* enu = [arr objectEnumerator];
	NSBundle* plugIn;
	
	while (plugIn = [enu nextObject])
		[[self class] disable:plugIn shouldAutoEnableOnVersionChange:YES];
}

- (NSString*) dottedListForPlugIns:(NSArray*) arr {
	NSMutableString* str = [NSMutableString string];
	
	NSEnumerator* enu = [arr objectEnumerator];
	NSBundle* bundle;
	int i = 0; BOOL isFirst = YES;
	while (bundle = [enu nextObject]) {
		if (i > 10) {
			[str appendFormat:@"\n %C (%C)", 0x2022, 0x2026];
			break;
		}
		
		NSString* name = [self userVisibleNameOfBundle:bundle];
		[str appendFormat:@"%@ %C %@", isFirst? @"" : @"\n", 0x2022, name];
		i++; isFirst = NO;
	}
	
	return str;
}

- (NSString*) userVisibleNameOfBundle:(NSBundle*) bundle {
	NSString* name;
	if (!(name = [[bundle infoDictionary] objectForKey:@"CFBundleName"]))
		name = [[NSFileManager defaultManager] displayNameAtPath:[bundle bundlePath]];
	return name;
}

- (NSString*) dottedListWithReasonsWithVersionTooHigh:(NSArray*) high
										versionTooLow:(NSArray*) low
										 incompatible:(NSArray*) incom {
	NSMutableString* str = [NSMutableString string];
	BOOL isFirst = YES;
	
	if ([high count] > 0) {
		if (!isFirst) [str appendString:@"\n"];
		isFirst = NO;
		[str appendString:L0PlugSuitLocalizedString(@"Can't load because this application is too new:\n", @"Header for 'Version too high' in multiple errors panel.")];
		[str appendFormat:@"%@\n", [self dottedListForPlugIns:high]];
	}
	
	if ([low count] > 0) {
		if (!isFirst) [str appendString:@"\n"];
		isFirst = NO;
		[str appendString:L0PlugSuitLocalizedString(@"Can't load because this application is too old:\n", @"Header for 'Version too low' in multiple errors panel.")];
		[str appendFormat:@"%@\n", [self dottedListForPlugIns:low]];
	}
	
	if ([incom count] > 0) {
		if (!isFirst) [str appendString:@"\n"];
		isFirst = NO;
		[str appendString:L0PlugSuitLocalizedString(@"Can't load because an error happened while loading them:\n", @"Header for 'Version too high' in multiple errors panel.")];
		[str appendFormat:@"%@\n", [self dottedListForPlugIns:incom]];
	}
	
	return str;
}

- (void) presentVersionTooHighForPlugIns:(NSArray*) arr {
	NSAlert* alert = [NSAlert new];
	[alert setMessageText:L0PlugSuitLocalizedString(@"PlugSuit didn't load some plug-ins because they require an older version of this application; they don't work with this version.", @"Version too high (only one reason) panel.")];
	[alert setInformativeText:[self dottedListForPlugIns:arr]];
	[alert runModal];
	[alert release];
}

- (void) presentVersionTooLowForPlugIns:(NSArray*) arr {
	NSAlert* alert = [NSAlert new];
	[alert setMessageText:L0PlugSuitLocalizedString(@"PlugSuit didn't load some plug-ins because they require a newer version of this application; they don't work with this version.", @"Version too low (only one reason) panel.")];
	[alert setInformativeText:[self dottedListForPlugIns:arr]];
	[alert runModal];
	[alert release];
}

- (void) presentIncompatibleForPlugIns:(NSArray*) arr {
	NSAlert* alert = [NSAlert new];
	[alert setMessageText:L0PlugSuitLocalizedString(@"An error happened while PlugSuit was loading some plug-ins.", @"Version too low (only one reason) panel.")];
	[alert setInformativeText:[self dottedListForPlugIns:arr]];
	[alert runModal];
	[alert release];
}

- (void) _presentErrorsTrampoline:(NSArray*) args {
	[self presentErrorsForVersionTooHigh:[args objectAtIndex:0] versionTooLow:[args objectAtIndex:1] incompatible:[args objectAtIndex:2]];
}

- (void) presentErrorsForVersionTooHigh:(NSArray*) high versionTooLow:(NSArray*) low incompatible:(NSArray*) incom {
	NSAlert* alert = [NSAlert new];
	[alert setMessageText:L0PlugSuitLocalizedString(@"PlugSuit could not load some plug-ins for this application. They have been disabled until you install newer versions of them, or you enable them again from PlugSuit's preference pane.", @"Version too low (only one reason) panel.")];
	[alert setInformativeText:[self dottedListWithReasonsWithVersionTooHigh:high versionTooLow:low incompatible:incom]];
	[alert runModal];
	[alert release];
}

- (void) presentVersionTooHighForPlugIn:(NSBundle*) plugIn {
	NSAlert* alert = [NSAlert new];
	[alert setMessageText:
		[NSString stringWithFormat:
		L0PlugSuitLocalizedString(@"PlugSuit didn't load the plug-in \"%@\" because it requires an older version of this application; it doesn't work with this version.", @"Version too high (only one plug-in) panel."),
			[self userVisibleNameOfBundle:plugIn]]];
	[alert setInformativeText:L0PlugSuitLocalizedString(@"The plug-in has been disabled.", @"Version too high (only one plug-in) panel info text.")];
	[alert runModal];
	[alert release];
}

- (void) presentVersionTooLowForPlugIn:(NSBundle*) plugIn {
	NSAlert* alert = [NSAlert new];
	[alert setMessageText:
		[NSString stringWithFormat:
			L0PlugSuitLocalizedString(@"PlugSuit didn't load the plug-in \"%@\" because it requires a newer version of this application; it doesn't work with this version.", @"Version too low (only one plug-in) panel."),
			[self userVisibleNameOfBundle:plugIn]]];
	[alert setInformativeText:L0PlugSuitLocalizedString(@"The plug-in has been disabled.", @"Version too low (only one plug-in) panel info text.")];
	[alert runModal];
	[alert release];
}

- (void) presentIncompatibleForPlugIn:(NSBundle*) plugIn {
	NSAlert* alert = [NSAlert new];
	[alert setMessageText:
		[NSString stringWithFormat:
			L0PlugSuitLocalizedString(@"PlugSuit didn't load the plug-in \"%@\" because there was an error while loading it.", @"Incompatible (only one plug-in) panel."),
			[self userVisibleNameOfBundle:plugIn]]];
	[alert setInformativeText:L0PlugSuitLocalizedString(@"The plug-in has been disabled.", @"Version too low (only one plug-in) panel info text.")];
	[alert runModal];
	[alert release];
}

- (NSBundle*) plugSuitBundle {
	return [NSBundle bundleWithIdentifier:@"net.infinite-labs.PlugSuit.Support"];
}

- (NSString*) localizedString:(NSString*) entry {
	NSBundle* b = [self plugSuitBundle];
	if (b)
		return [b localizedStringForKey:entry value:entry table:nil];
	else
		return entry;
}

+ (BOOL) plugInsExistFor:(NSBundle*) app processID:(pid_t) pid {
	NSEnumerator* enu = [[self registries] objectEnumerator];
	L0PlugSuitRegistry* registry;
	
	while (registry = [enu nextObject]) {
		if ([registry plugInsExistFor:app processID:pid])
			return YES;
	}
	
	return NO;
}

+ (void) updateRegistries {
	[[self registries] makeObjectsPerformSelector:@selector(updateRegistry)];
}

+ (void) loadPlugIns {
	[[self registries] makeObjectsPerformSelector:@selector(loadPlugIns)];
}

+ (NSDictionary*) registeredPlugIns {
	NSEnumerator* enu = [[self registries] objectEnumerator];
	L0PlugSuitRegistry* registry;
	NSMutableDictionary* dict = [NSMutableDictionary new];
	
	while (registry = [enu nextObject]) {
		[dict addEntriesFromDictionary:[registry registeredPlugIns]];
	}
	
	return dict;
}

- (BOOL) plugInsExistFor:(NSBundle*) app processID:(pid_t) pid {
	return NO; // abstract
}

- (void) updateRegistry { /* abstract */ }
- (void) loadPlugIns { /* abstract */ }

- (NSDictionary*) registeredPlugIns { return nil; /* abstract */ }

+ (void) enable:(NSBundle*) bundle {
	if (![bundle bundleIdentifier] || [[bundle bundleIdentifier] isEqual:@""])
		return;
	
	NSMutableDictionary* dict = [self disabledPlugInsMutableDictionary];
	[dict removeObjectForKey:[bundle bundleIdentifier]];
	
	[self setDisabledPlugInsDictionary:dict];
}

+ (void) disable:(NSBundle*) bundle shouldAutoEnableOnVersionChange:(BOOL) autoEnable {
	if (![bundle bundleIdentifier] || [[bundle bundleIdentifier] isEqual:@""])
		return;
	
	NSMutableDictionary* dict = [self disabledPlugInsMutableDictionary];
	NSMutableDictionary* bundleDetails = [NSMutableDictionary dictionary];
	
	id v;
	if (v = [[bundle infoDictionary] objectForKey:@"CFBundleVersion"])
		[bundleDetails setObject:v forKey:@"Version"];
	[bundleDetails setObject:[NSNumber numberWithBool:autoEnable] forKey:@"AutoEnableOnVersionChange"];
	
	[dict setObject:bundleDetails forKey:[bundle bundleIdentifier]];
	[self setDisabledPlugInsDictionary:dict];
}

static NSDictionary* _disabledPlugIns = nil;

+ (NSDictionary*) disabledPlugInsDictionary {
	if (!_disabledPlugIns)
		_disabledPlugIns = [[self updatedDisabledPlugInsDictionary] retain];
	
	return _disabledPlugIns;
}

+ (void) clearDisabledPlugInsCache {
	[_disabledPlugIns release];
	_disabledPlugIns = nil;
}

+ (NSMutableDictionary*) disabledPlugInsMutableDictionary {
	NSDictionary* dict = [self disabledPlugInsDictionary];
	if (dict)
		return [NSMutableDictionary dictionaryWithDictionary:dict];
	else
		return [NSMutableDictionary dictionary];
}

+ (NSDictionary*) updatedDisabledPlugInsDictionary {
	CFPreferencesAppSynchronize((CFStringRef) kL0PlugSuitSharedPreferences);
	id dict = (id) CFPreferencesCopyAppValue((CFStringRef) @"DisabledPlugIns",
										(CFStringRef) kL0PlugSuitSharedPreferences);
	return [dict autorelease];
}

+ (void) setDisabledPlugInsDictionary:(NSDictionary*) dict {
	if (dict != _disabledPlugIns) {
		[_disabledPlugIns release];
		_disabledPlugIns = [dict retain];
	}

	CFPreferencesSetAppValue((CFStringRef) @"DisabledPlugIns",
							 dict,
							 (CFStringRef) kL0PlugSuitSharedPreferences);
	CFPreferencesAppSynchronize((CFStringRef) kL0PlugSuitSharedPreferences);
	
	[[NSDistributedNotificationCenter defaultCenter]
		postNotificationName:@"L0PlugSuitPreferencesChanged" object:@"net.infinite-labs.PlugSuit"];
}

+ (BOOL) isEnabled:(NSBundle*) bundle {
	if (![bundle bundleIdentifier] || [[bundle bundleIdentifier] isEqual:@""])
		return NO;
	else {
		NSDictionary* dict = [self disabledPlugInsDictionary];
		NSDictionary* info;
		if (info = [dict objectForKey:[bundle bundleIdentifier]]) {
			if ([[info objectForKey:@"AutoEnableOnVersionChange"] boolValue] &&
				![[info objectForKey:@"Version"] isEqual:[[bundle infoDictionary] objectForKey:@"CFBundleVersion"]]) {
				NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithDictionary:dict];
				[dict removeObjectForKey:[bundle bundleIdentifier]];
				[self setDisabledPlugInsDictionary:dict];
				return YES;
			} else
				return NO;
		} else
			return YES;
	}
}

- (NSArray*) versionTooHighOnLastLoad { return nil; /* abstract */ }
- (NSArray*) versionTooLowOnLastLoad { return nil; /* abstract */ }
- (NSArray*) incompatibleOnLastLoad { return nil; /* abstract */ }

+ (NSArray*) versionTooHighOnLastLoad { 
	NSEnumerator* enu = [[self registries] objectEnumerator];
	L0PlugSuitRegistry* reg;
	NSMutableArray* result = [NSMutableArray array];
	
	while (reg = [enu nextObject]) {
		id v = [reg versionTooHighOnLastLoad];
		if (v)
			[result addObjectsFromArray:v];
	}
	
	return result;
}

+ (NSArray*) versionTooLowOnLastLoad { 
	NSEnumerator* enu = [[self registries] objectEnumerator];
	L0PlugSuitRegistry* reg;
	NSMutableArray* result = [NSMutableArray array];
	
	while (reg = [enu nextObject]) {
		id v = [reg versionTooLowOnLastLoad];
		if (v)
			[result addObjectsFromArray:v];
	}
	
	return result;
}

+ (NSArray*) incompatibleOnLastLoad { 
	NSEnumerator* enu = [[self registries] objectEnumerator];
	L0PlugSuitRegistry* reg;
	NSMutableArray* result = [NSMutableArray array];
	
	while (reg = [enu nextObject]) {
		id v = [reg incompatibleOnLastLoad];
		if (v)
			[result addObjectsFromArray:v];
	}
	
	return result;
}

static id _myself = nil;

+ (id) sharedInstance {
	if (!_myself)
		_myself = [self new];
	return _myself;
}

+ (void) releaseSharedInstance {
	[_myself release];
	_myself = nil;
}

@end
